package org.uiot.process

import groovy.transform.CompileStatic
import org.apache.flink.api.java.tuple.Tuple5
import org.uiot.protocol.PacketTraits
import org.uiot.protocol.codec.BitOutputStream
import org.uiot.protocol.codec.BitSource
import org.uiot.protocol.codec.CachedRule
import org.uiot.protocol.group.CachedSequence
import org.uiot.protocol.group.Groupable
import org.uiot.protocol.group.Subjection

@CompileStatic
abstract class Codec implements Serializable {
    static enum Mode {
        ENCODING(0), DECODING(1)
        final int value

        private Mode(int value) { this.value = value }

        static Mode byValue(int val) {
            values().find { it.value == val }
        }
    }

    /**
     * 缓存的（编解码）基础规则
     * Key: Code of rule; Value: Instance of CachedRule
     */
    protected final Map<String, CachedRule> CachedRules

    /**
     * 缓存的编解码分组（映射关系）
     * 缓存结构：根<编解码分组类型, 该类型分组缓存<内置分组类型, 按隶属关系缓存的编解码分组缓存<隶属关系代码,绑定该隶属关系的编解码分组集合>>>
     * 示例: <Sequence: <Packet: [01: [...],02: [...],03: [...]]>>
     * 示例: <KeyValue: <Packet: [01: [...],02: [...],03: [...]]>>
     * 示例: <Sequence: <KeyValue: [abc: [...],edf: [...],ghi: [...]]>>
     * 示例: <Calculation: <Packet: [01: [...],02: [...],03: [...]]>>
     */
    protected final Map<Groupable.Type, Map<Subjection, Map<Serializable, List<Groupable>>>> CachedGroups

    /**
     * 缓存的通讯封包特征
     */
    protected final Map<Integer, PacketTraits> CachedPackets

    protected Codec() {
        CachedRules = [:]
        CachedGroups = [:]
        CachedPackets = [:]
    }

    Codec cachePackets(List<PacketTraits> packets) {
        packets.each { PacketTraits packet ->
            CachedPackets.put(packet.code, packet)
        }
        this
    }

    Codec cacheRules(List<CachedRule> rules) {
        rules.each { CachedRule rule ->
            CachedRules.put(rule.code, rule)
        }
        this
    }

    Codec cacheSequenceGroups(List<CachedSequence> groups) {
        def cachedSequences = CachedGroups.getOrDefault(Groupable.Type.Sequence, [:])

        if (cachedSequences.isEmpty())
            CachedGroups.put(Groupable.Type.Sequence, cachedSequences)

        groups.each { CachedSequence group ->
            group.cachedRule = CachedRules[group.ruleCode]

            if (group.part == PacketTraits.Part.Body) {
                def cache4parentType = cachedSequences.getOrDefault(group.parentType, [:])

                if (cachedSequences.isEmpty())
                    cachedSequences.put(group.parentType, cache4parentType)

                def cache4parentCode = cache4parentType.getOrDefault(group.parentCode, [])

                if (cache4parentCode.isEmpty())
                    cache4parentType.put(group.parentCode, cache4parentCode)

                cache4parentCode.add(group)
            }
        }

        cachedSequences.each { subjection -> subjection.value.each { it.value.sort() } }
        this
    }

    protected Map decode(Integer packetCode, byte[] contents) {
        def packetDef = this.CachedPackets[packetCode]
//        def isData = packetDef instanceof DataTraits

        def bitSrc = new BitSource(contents), decodedContents = [:]

        switch (packetDef?.bodyGroupType?.request) {
            case Groupable.Type.Sequence:
                CachedGroups[packetDef.bodyGroupType.request][Subjection.Packet][packetCode]?.each { cachedSequence ->
                    ((CachedSequence) cachedSequence).decode(bitSrc, decodedContents)
                }
                break
        }

        decodedContents
    }

    protected byte[] encode(Integer packetCode, Map contents) {
        def packetDef = this.CachedPackets[packetCode]

        def byteArrayOutputStream = new ByteArrayOutputStream()
        def bitOutputStream = new BitOutputStream(byteArrayOutputStream)

        switch (packetDef?.bodyGroupType?.response) {
            case Groupable.Type.Sequence:
                CachedGroups[packetDef.bodyGroupType.response][Subjection.Packet][packetCode].each { cachedSequence ->
                    ((CachedSequence) cachedSequence).encode(bitOutputStream, contents)
                }
                break
        }

        byteArrayOutputStream.toByteArray()
    }

    static Codec getInstance(Mode mode) {
        if (mode == Mode.DECODING) {
            return new Decoding()
        } else if (mode == Mode.ENCODING) {
            return new Encoding()
        } else {
            return null
        }
    }

    /**
     * String : objectId
     * Integer : packetCode
     * Date : dateReceived
     * Map : metadata
     * Map : contents
     */
    final static class UnencodedPacket extends Tuple5<String, Integer, Date, HashMap<String, ?>, Map<String, ?>> {
        UnencodedPacket() {
            super()
        }

        UnencodedPacket(String objectId, Integer packetCode, Date dateReceived, HashMap<String, ?> metadata, Map<String, ?> contents) {
            super(objectId, packetCode, dateReceived, metadata, contents)
        }
    }
    /**
     * String : objectId
     * Integer : packetCode
     * Date : dateReceived
     * Map : metadata
     * byte[] : contents
     */
    final static class EncodedPacket extends Tuple5<String, Integer, Date, HashMap<String, ?>, byte[]> {
        EncodedPacket() {
            super()
        }

        EncodedPacket(String objectId, Integer packetCode, Date dateReceived, HashMap<String, ?> metadata, byte[] contents) {
            super(objectId, packetCode, dateReceived, metadata, contents)
        }

        byte[] getContents() {
            this.f4
        }
    }

    /**
     * String : objectId
     * Integer : packetCode
     * Date : dateReceived
     * Map : metadata
     * byte[] : contents
     */
    final static class UndecodedPacket extends Tuple5<String, Integer, Date, HashMap<String, ?>, byte[]> {
        UndecodedPacket() {
            super()
        }

        UndecodedPacket(String objectId, Integer packetCode, Date dateReceived, HashMap<String, ?> metadata, byte[] contents) {
            super(objectId, packetCode, dateReceived, metadata, contents)
        }
    }
    /**
     * String : objectId
     * Integer : packetCode
     * Date : dateReceived
     * Map : metadata
     * Map : contents
     */
    final static class DecodedPacket extends Tuple5<String, Integer, Date, HashMap<String, ?>, Map<String, ?>> {
        DecodedPacket() {
            super()
        }

        DecodedPacket(String objectId, Integer packetCode, Date dateReceived, HashMap<String, ?> metadata, Map<String, ?> contents) {
            super(objectId, packetCode, dateReceived, metadata, contents)
        }
    }
}
